#include "EstrategiaRecursoEscrituraDiferida.h"
#include "RecursoAlmacenamiento.h"

namespace Toolkit
{

EstrategiaRecursoEscrituraDiferida::EstrategiaRecursoEscrituraDiferida(
		const TipoSerializacion tserializacion,
		const unsigned long int &tamanoBloque, const bool &controlarUnicidad) :
	EstrategiaRecurso(tserializacion, tamanoBloque, controlarUnicidad)
{
	this->unidadUltima = -1;
	this->unidadPrimera = -1;
	this->recursoOcupado = false;

	this->claveAux = NULL;

}

EstrategiaRecursoEscrituraDiferida::~EstrategiaRecursoEscrituraDiferida()
{
	flushBuffer();

	if (this->claveAux != NULL)
	{
		delete this->claveAux;
	}
}

void EstrategiaRecursoEscrituraDiferida::guardarDato(const Dato &dato,
		const Clave &clave)
{
	if(this->recursoOcupado) {
		throw RecursoOcupadoException();
	}

	if (this->claveAux == NULL)
	{
		this->claveAux = clave.clonar();
	}

	if (getControlUnicidad())
	{
		bool encontrado = false;
		Dato *datoAux = dato.clonar();

		//busco en secundario
		encontrado = (busquedaSecuencialLRU((*datoAux), clave) != -1);

		if (!encontrado)
		{
			WrapperComponente wrapperPrimario;
			encontrado = buscarEnPrimario(clave, *datoAux, wrapperPrimario); //busco en primario
		}

		delete datoAux;

		if (encontrado)
		{
			throw ClaveDuplicadaException();
		}
	}

	long int posicionEnMemoria = 0;

	WrapperBufferDiferido wrapperAuxiliar;
	try
	{
		posicionEnMemoria
				= getRecurso()->estrategiaAlmacenamiento->guardarDato(
						*getRecurso()->secundario, dato, clave, wrapperAuxiliar);

	} catch (BufferOverflowException &e)
	{
		liberarUnidadMenosUsada();

		posicionEnMemoria
				= getRecurso()->estrategiaAlmacenamiento->guardarDato(
						*getRecurso()->secundario, dato, clave, wrapperAuxiliar);
	}

	if ((wrapperAuxiliar.getDireccionUnidad() != -1)
			&& (this->getRecurso()->indice != NULL))
	{
		this->getRecurso()->indice->insertar(clave,
				wrapperAuxiliar.getDireccionUnidad());
	}//Asegurarse de que esa direccion sea siempre consistente (las estrategias almacenamiento, si reutilizan el wrapper tienen que hacer un clonar)

	actualizarUnidadUsada(posicionEnMemoria, true);

}

bool EstrategiaRecursoEscrituraDiferida::recuperarDato(const Clave &clave,
		Dato &dato)
{
	if(this->recursoOcupado) {
		throw RecursoOcupadoException();
	}

	//busco en secundario
	long int posicionEnSecundario = busquedaSecuencialLRU(dato, clave);
	if (posicionEnSecundario != -1)
	{
		actualizarUnidadUsada(posicionEnSecundario, false);
		return true;
	}

	//Si no esta busco en primario
	unsigned long int posicionEnPrimario;
	WrapperComponente wrapperPrimario;
	bool encontrado = buscarEnPrimario(clave, dato, wrapperPrimario,
			posicionEnPrimario);

	if (encontrado)
	{
		getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
				(*getRecurso()->primario), posicionEnPrimario, wrapperPrimario);

		WrapperBufferDiferido wrapperSecundario;
		wrapperSecundario.setComponente(wrapperPrimario.getComponente());
		wrapperSecundario.setDireccionUnidad(posicionEnPrimario);
		wrapperSecundario.setDirtyBit(false);

		try
		{
			posicionEnSecundario
					= getRecurso()->estrategiaAlmacenamiento->guardarComponente(
							(*(getRecurso()->secundario)), wrapperSecundario);

		} catch (BufferOverflowException &e)
		{
			liberarUnidadMenosUsada();

			posicionEnSecundario
					= getRecurso()->estrategiaAlmacenamiento->guardarComponente(
							(*(getRecurso()->secundario)), wrapperSecundario);
		}

		delete wrapperPrimario.getComponente();

		actualizarUnidadUsada(posicionEnSecundario, false);

		return true;
	}

	return false;
}

void EstrategiaRecursoEscrituraDiferida::eliminarDato(const Clave &clave,
		Dato &dato)
{
	if(this->recursoOcupado) {
		throw RecursoOcupadoException();
	}

	//si esta en buffer lo elimino.
	long int posicionEnPrimario = -1;
	long int posicionEnSecundario = busquedaSecuencialLRU(dato, clave);

	if (posicionEnSecundario != -1)
	{
		WrapperBufferDiferido wrapperSecundario;

		unsigned long int cantidadAnterior =
				this->getRecurso()->secundario->espacioLibre();

		getRecurso()->estrategiaAlmacenamiento->eliminarDato(
				(*(getRecurso()->secundario)), posicionEnSecundario, dato,
				clave, wrapperSecundario);

		if (cantidadAnterior < this->getRecurso()->secundario->espacioLibre())
		{
			//el componente se retiro del buffer, debe retirarse su informacion
			posicionEnPrimario = wrapperSecundario.getDireccionUnidad();

			long int anterior = wrapperSecundario.getUnidadAnterior();
			long int siguiente = wrapperSecundario.getUnidadSiguiente();

			actualizarUnidadEliminada(posicionEnSecundario, anterior, siguiente);
		}
		else
		{
			//si el componente no se elimino, lo marco con dirty bit
			getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
					(*(getRecurso()->secundario)), posicionEnSecundario,
					wrapperSecundario);

			wrapperSecundario.setDirtyBit(true);

			getRecurso()->estrategiaAlmacenamiento->guardarComponente(
					(*(getRecurso()->secundario)), posicionEnSecundario,
					wrapperSecundario);

			delete wrapperSecundario.getComponente();
		}
	}

	//lo elimino del archivo, si esta
	WrapperComponente wrapperPrimario;
	bool encontrado = (posicionEnPrimario != -1);

	if (!encontrado)
	{
		unsigned long int posAux;
		encontrado = buscarEnPrimario(clave, dato, wrapperPrimario, posAux);
		posicionEnPrimario = posAux;
	}

	if (encontrado)
	{
		getRecurso()->estrategiaAlmacenamiento->eliminarDato(
				(*(getRecurso()->primario)), posicionEnPrimario, dato, clave,
				wrapperPrimario);

		if (getRecurso()->indice != NULL)
		{
			getRecurso()->indice->eliminar(clave);
		}
	}

}

void EstrategiaRecursoEscrituraDiferida::actualizarDato(const Dato &dato,
		const Clave &clave)
{
	if(this->recursoOcupado) {
		throw RecursoOcupadoException();
	}

	Dato *datoAux = dato.clonar();
	long int posicionEnSecundario = busquedaSecuencialLRU(*datoAux, clave);
	delete datoAux;

	if (posicionEnSecundario != -1)
	{
		WrapperBufferDiferido wrapperSecundario;
		long int nuevaPosicion;
		try
		{
			//Recupero el componente para que el wrapper se hidrate con la informacion correcta.
			getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
					(*(getRecurso()->secundario)), posicionEnSecundario,
					wrapperSecundario);
			delete wrapperSecundario.getComponente();

			nuevaPosicion
					= getRecurso()->estrategiaAlmacenamiento->modificarDato(
							(*(getRecurso()->secundario)), dato, clave,
							posicionEnSecundario, wrapperSecundario);

		} catch (BufferOverflowException &e)
		{
			liberarUnidadMenosUsada();

			nuevaPosicion
					= getRecurso()->estrategiaAlmacenamiento->modificarDato(
							(*(getRecurso()->secundario)), dato, clave,
							posicionEnSecundario, wrapperSecundario);
		}

		if (nuevaPosicion != posicionEnSecundario)
		{
			//hay que sacar la direccion en disco que se guardo con el wrapper
			getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
					(*(getRecurso()->secundario)), nuevaPosicion,
					wrapperSecundario);

			long int posicionEnPrimario =
					wrapperSecundario.getDireccionUnidad();
			if ((posicionEnPrimario != -1) && (getRecurso()->indice != NULL))
			{
				getRecurso()->indice->eliminar(clave);
			}
			wrapperSecundario.setDireccionUnidad(-1);

			getRecurso()->estrategiaAlmacenamiento->guardarComponente(
					(*(getRecurso()->secundario)), nuevaPosicion,
					wrapperSecundario);

			delete wrapperSecundario.getComponente();
		}

		actualizarUnidadUsada(nuevaPosicion, true);
	}
}

void EstrategiaRecursoEscrituraDiferida::flushBuffer()
{
	this->recursoOcupado = true;

	long int posicionBloque = this->unidadPrimera;

	while (posicionBloque != -1)
	{
		posicionBloque = flushComponente(posicionBloque);
	}

	this->recursoOcupado = false;

}

long int EstrategiaRecursoEscrituraDiferida::flushComponente(
		const unsigned long int &posicionEnBuffer)
{
	WrapperBufferDiferido wrapperSecundario;
	getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
			(*(getRecurso()->secundario)), posicionEnBuffer, wrapperSecundario);

	if (wrapperSecundario.getDirtyBit())
	{
		WrapperComponente wrapperPrimario;
		wrapperPrimario.setComponente(wrapperSecundario.getComponente());

		if (wrapperSecundario.getDireccionUnidad() != -1)
		{
			getRecurso()->estrategiaAlmacenamiento->guardarComponente(
					(*(getRecurso()->primario)),
					wrapperSecundario.getDireccionUnidad(), wrapperPrimario);
		}
		else
		{
			unsigned long int posicionEnPrimario =
					getRecurso()->estrategiaAlmacenamiento->guardarComponente(
							(*(getRecurso()->primario)), wrapperPrimario);
			wrapperSecundario.setDireccionUnidad(posicionEnPrimario);

			if (getRecurso()->indice != NULL)
			{

				list<Clave*> listaClaves =
						wrapperSecundario.getComponente()->getClaves(*claveAux);

				for (list<Clave*>::const_iterator it = listaClaves.begin(); it
						!= listaClaves.end(); it++)
				{
					this->getRecurso()->indice->insertar((**it),
							wrapperSecundario.getDireccionUnidad());
					delete (*it);
				}
			}
		}

		wrapperSecundario.setDirtyBit(false);
		getRecurso()->estrategiaAlmacenamiento->guardarComponente(
				(*(getRecurso()->secundario)), posicionEnBuffer,
				wrapperSecundario);

	}

	long int siguiente = wrapperSecundario.getUnidadSiguiente();
	delete wrapperSecundario.getComponente();
	return siguiente;

}

void EstrategiaRecursoEscrituraDiferida::liberarUnidadMenosUsada()
{
	this->recursoOcupado = true;

	WrapperBufferDiferido wrapperAuxiliar;

	long int ultimaAnterior = this->unidadUltima;
	getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
			*getRecurso()->secundario, this->unidadUltima, wrapperAuxiliar);

	this->unidadUltima = wrapperAuxiliar.getUnidadAnterior();
	flushComponente(ultimaAnterior);

	getRecurso()->estrategiaAlmacenamiento->eliminarComponente(
			(*(getRecurso()->secundario)), ultimaAnterior, wrapperAuxiliar);

	delete wrapperAuxiliar.getComponente();

	//pongo el siguiente de el nuevo ultimo en -1
	getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
			*getRecurso()->secundario, this->unidadUltima, wrapperAuxiliar);

	wrapperAuxiliar.setUnidadSiguiente(-1);

	getRecurso()->estrategiaAlmacenamiento->guardarComponente(
			*getRecurso()->secundario, this->unidadUltima, wrapperAuxiliar);

	delete wrapperAuxiliar.getComponente();

	this->recursoOcupado = false;

}

long int EstrategiaRecursoEscrituraDiferida::busquedaSecuencialLRU(Dato &dato,
		const Clave &clave)
{
	long int posicionBloque = this->unidadPrimera;

	while (posicionBloque != -1)
	{
		WrapperBufferDiferido wrapper;

		bool resultado = getRecurso()->estrategiaAlmacenamiento->recuperarDato(
				(*(getRecurso()->secundario)), posicionBloque, dato, clave,
				wrapper);

		if (resultado && (clave == dato))
		{
			return posicionBloque;
		}

		posicionBloque = wrapper.getUnidadSiguiente();
	}

	return -1;
}

void EstrategiaRecursoEscrituraDiferida::actualizarUnidadUsada(
		const long int &posicionEnBuffer, const bool &setDirtyBit)
{
	this->recursoOcupado = true;

	WrapperBufferDiferido wrapperActualizado, wrapperAuxiliar;

	getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
			*getRecurso()->secundario, posicionEnBuffer, wrapperActualizado);

	if (this->unidadPrimera == -1 && this->unidadUltima == -1)
	{
		this->unidadUltima = posicionEnBuffer;

	}
	else if (this->unidadPrimera != posicionEnBuffer)
	{
		//Seteo el anterior primero, como el segundo
		getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
				*getRecurso()->secundario, this->unidadPrimera, wrapperAuxiliar);
		wrapperAuxiliar.setUnidadAnterior(posicionEnBuffer);
		getRecurso()->estrategiaAlmacenamiento->guardarComponente(
				*getRecurso()->secundario, this->unidadPrimera, wrapperAuxiliar);

		delete wrapperAuxiliar.getComponente();

		//seteo al anterior del nuevo primero, para que deje de apuntarlo como siguiente
		if (wrapperActualizado.getUnidadAnterior() != -1)
		{
			getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
					*getRecurso()->secundario,
					wrapperActualizado.getUnidadAnterior(), wrapperAuxiliar);
			wrapperAuxiliar.setUnidadSiguiente(
					wrapperActualizado.getUnidadSiguiente());
			getRecurso()->estrategiaAlmacenamiento->guardarComponente(
					*getRecurso()->secundario,
					wrapperActualizado.getUnidadAnterior(), wrapperAuxiliar);

			delete wrapperAuxiliar.getComponente();

			if (wrapperActualizado.getUnidadSiguiente() != -1)
			{
				getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
						*getRecurso()->secundario,
						wrapperActualizado.getUnidadSiguiente(),
						wrapperAuxiliar);

				wrapperAuxiliar.setUnidadAnterior(
						wrapperActualizado.getUnidadAnterior());

				getRecurso()->estrategiaAlmacenamiento->guardarComponente(
						*getRecurso()->secundario,
						wrapperActualizado.getUnidadSiguiente(),
						wrapperAuxiliar);

				delete wrapperAuxiliar.getComponente();

			}
		}

		wrapperActualizado.setUnidadSiguiente(this->unidadPrimera);
	}

	this->unidadPrimera = posicionEnBuffer;

	if (setDirtyBit)
	{
		wrapperActualizado.setDirtyBit(true);
	}

	wrapperActualizado.setUnidadAnterior(-1);

	getRecurso()->estrategiaAlmacenamiento->guardarComponente(
			*getRecurso()->secundario, posicionEnBuffer, wrapperActualizado);

	delete wrapperActualizado.getComponente();

	this->recursoOcupado = false;
}

void EstrategiaRecursoEscrituraDiferida::actualizarUnidadEliminada(
		const long int &posicionEnSecundario, const long int &anterior,
		const long int &siguiente)
{
	this->recursoOcupado = true;

	WrapperBufferDiferido wrapperAnterior;
	WrapperBufferDiferido wrapperSiguiente;

	if (anterior != -1)
	{
		getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
				(*(getRecurso()->secundario)), anterior, wrapperAnterior);

		wrapperAnterior.setUnidadSiguiente(siguiente);

		getRecurso()->estrategiaAlmacenamiento->guardarComponente(
				(*(getRecurso()->secundario)), anterior, wrapperAnterior);
		delete wrapperAnterior.getComponente();
	}

	if (siguiente != -1)
	{
		getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
				(*(getRecurso()->secundario)), siguiente, wrapperSiguiente);

		wrapperSiguiente.setUnidadAnterior(anterior);

		getRecurso()->estrategiaAlmacenamiento->guardarComponente(
				(*(getRecurso()->secundario)), siguiente, wrapperSiguiente);
		delete wrapperSiguiente.getComponente();
	}

	if (this->unidadPrimera == posicionEnSecundario)
	{
		if (siguiente == -1)
		{
			throw "Esto apesta";
		}

		this->unidadPrimera = siguiente;
	}
	if (this->unidadUltima == posicionEnSecundario)
	{
		if (anterior == -1)
		{
			throw "esto apesta";
		}
		this->unidadUltima = anterior;
	}

	this->recursoOcupado = false;
}

void EstrategiaRecursoEscrituraDiferida::imprimirCabezaLista(
		const unsigned long int &elementos) const
{
	long int posicionBloque = this->unidadPrimera;
	unsigned int i = 0;
	while ((posicionBloque != -1) && (i < elementos))
	{
		WrapperBufferDiferido wrapper;

		getRecurso()->estrategiaAlmacenamiento->recuperarComponente(
				(*(getRecurso()->secundario)), posicionBloque, wrapper);

		cout << posicionBloque << "  ";

		posicionBloque = wrapper.getUnidadSiguiente();
		i++;

		delete wrapper.getComponente();
	}

	cout << endl;

}

}
